#pragma once 
#include <sys/wait.h>
#include <cstdlib>
#include <iostream>
#include <unistd.h>
#include "Utility.hpp"
#include "LOG.hpp"
#include <string>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <algorithm>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/sendfile.h>


#define OK 200
#define NOT_FOUND 404
#define ERROR_SERVER 500
#define VERSION "http/1.1"
#define LINE_END "\r\n"


static std::string SuffixDesc(std::string& target){
    std::unordered_map<std::string,std::string> suffix={
        {".html","text/html"},
        {".css","text/css"},
        {".js","application/javascript"},
        {".png","application/x-png"},
        {".jpg","application/x-jpg"}
    };
    auto item = suffix.find(target);
    if(item != suffix.end()){
        return item->second;
    }
        return "text/html";
}

static std::string StatusCodeDesc(int code){
    if(code == 200){
        return "OK";
    }
    else if(code == 404){
        return "NOT_FOUND";
    }
    else if(code == 500){
        return "ERROR_SERVER";
    }
    return "OK";
}

static std::string GetErrorFile(int code){
    std::string s = "wwwroot/";
    s += std::to_string(code);
    s += ".html";
    return s;
}


class HttpRequest{
    public:
        std::string request_line;
        std::vector<std::string> request_header;
        std::string blank;
        std::string request_body;

        std::string method;
        std::string path;
        std::string uri;
        std::string version;
        std::unordered_map<std::string,std::string> request_header_kv;
        std::string query;
        std::string suffix;

        int content_length;
        int cgi = false;
        int size = 0;
};
class HttpResbonse{
    public:
        std::string resbonse_line;
        std::vector<std::string> resbonse_header;
        std::string blank = LINE_END;
        std::string resbonse_body;
        
        int status_code;
        int fd;
};

class EndPoint{
    private:
        int sock;
        bool stop;
        HttpRequest http_request;
        HttpResbonse http_resbonse;
    public:
        EndPoint(int _sock):sock(_sock),stop(false){}


        int IsStop(){
            return stop;
        } 

        void RecvHttpRequest(){
            if((!Recv_Parse_HttpRequest_Line()) && \
                (!Recv_Parse_HttpRequest_Head()) && \
                (!Recv_HttpRequest_Body())){}
            }

        int  Recv_Parse_HttpRequest_Line(){
            LOG(INFO,"Begin Recv Request"); 
            //recv
            if(Utility::ReadLine(sock,http_request.request_line)> 0){
                http_request.request_line.back() = 0;
                LOG(INFO,http_request.request_line);
            }
            else{
                stop = true;
                return stop;
            }
            //parse
            std::stringstream ss(http_request.request_line);
            ss>>http_request.method>>http_request.uri>>http_request.version;
            //toupper
            auto& mt = http_request.method;
            std::transform(mt.begin(),mt.end(),mt.begin(),::toupper);

            size_t pos = http_request.path.rfind(".");
            if(pos != std::string::npos){
                http_request.suffix = http_request.path.substr(pos+1);
            }
            return stop;
        }

        int Recv_Parse_HttpRequest_Head(){
            //recv
            std::string line = "";
            while(true){
                line.clear();
                if(Utility::ReadLine(sock,line)<=0){
                    stop = true;
                    return stop;
                }
                if(line == "\n"){
                    http_request.blank = line;
                    break;
                }
                line.back() = 0;
                http_request.request_header.push_back(line);
                LOG(INFO,line);
            }

            //parse
            for(auto& item:http_request.request_header) {
                std::string key,value;
                Utility::CutString(item,key,value,": ");
                http_request.request_header_kv[key] = value;
            }
            return stop;
        }

        int Recv_HttpRequest_Body(){
            auto& mt = http_request.method;
            if(mt == "POST"){
                auto item = http_request.request_header_kv.find("Content-Length");
                if(item != http_request.request_header_kv.end()){
                    http_request.content_length = atoi(item->second.c_str());
                    int size = http_request.content_length;
                    while(size){
                        char ch;
                        size_t s = recv(sock,&ch,1,0);
                        if(s > 0){
                            http_request.request_body.push_back(ch);
                            size--;
                        }
                        else if(s == 0){
                            break;
                        }
                        else {
                            stop = true;
                            return stop;
                            LOG(ERROR,"Recv RequestBody Error");
                            exit(1);
                        }
                    }
                }
            }
            return stop;
        }

        void Build_HttpResbonse(){
            http_request.path = "wwwroot";
            auto& mt = http_request.method;
            std::string tmp;
            struct stat buf;


            //检查方法是否合法
            if(mt!= "GET" && mt != "POST"){
                LOG(INFO,"Request Method Is Not Law!");
                http_resbonse.status_code = NOT_FOUND;
                goto END;
            }
            if(mt == "GET"){
                //如果是get方法,检查是否带参
                size_t pos = http_request.uri.find("?"); 
                if(pos != std::string::npos){
                    Utility::CutString(http_request.uri,tmp,http_request.query,"?");
                    http_request.cgi = true;
                }
                else {
                    tmp = http_request.uri;
                }
            }
            else if(mt == "POST"){
                tmp = http_request.uri;
                http_request.cgi = true;
            }
            else{}


            //拼接web根目录
            http_request.path += tmp;
            if(http_request.path.back() == '/'){
                http_request.path += "index.html";
            }

            //检查资源是否存在
            if(!stat(http_request.path.c_str(),&buf)){
                //检查资源是否是目录
                if(S_ISDIR(buf.st_mode)){
                    http_request.path += "/";
                    http_request.path += "index.html";
                    stat(http_request.path.c_str(),&buf);
                }//检查资源是否是可自行文件 
                else if((S_IXUSR & buf.st_mode)||(S_IXGRP & buf.st_mode)||(S_IXOTH & buf.st_mode)){
                    http_request.cgi = true;
                }
                http_request.size = buf.st_size;
            }
            else {
                LOG(INFO,http_request.path +"Resorce Is Not Exist");
                http_resbonse.status_code = NOT_FOUND;
                goto END;
            }

            if(http_request.cgi){
                http_resbonse.status_code = HandlerCgi();
            }
            else{
                http_resbonse.status_code = HandlerNonCgi();
            }
END:
            Build_HttpResbonseHelper();
        }

        int HandlerNonCgi(){
            auto& path = http_request.path;
            http_resbonse.fd = open(path.c_str(),O_RDONLY);
            if(http_resbonse.fd > 0){
                return OK;
            }
            return NOT_FOUND;

        }
        int HandlerCgi(){
            auto& bin = http_request.path;
            auto& query_get = http_request.query;
            auto& query_post = http_request.request_body;
            int upfd[2],downfd[2];
            if(pipe(upfd)<0 || pipe(downfd)<0){
                LOG(ERROR,"PIPE ERROR");
                return ERROR_SERVER;
            }

            pid_t pid = fork();
            if(pid == 0){
                close(upfd[0]);
                close(downfd[1]);

                dup2(downfd[0],0);
                dup2(upfd[1],1);

                 std::string method = "METHOD=";
                method += http_request.method;
                putenv((char*)method.c_str());          

                if(http_request.method == "GET"){
                    std::string query = "QUERY=";
                    query += query_get;
                    putenv((char*)query.c_str());
                }
                else if(http_request.method == "POST"){
                    int cl = http_request.content_length;
                    std::string content_length = "CONTENT_LENGTH=";
                    content_length += std::to_string(cl);
                    putenv((char*)content_length.c_str());
                }

                execl(bin.c_str(),bin.c_str(),nullptr);
                exit(1);
            }
            else if(pid > 0){
                close(upfd[1]);
                close(downfd[0]);

                std::string& method = http_request.method;
                if(method == "POST"){
                    const char* query = query_post.c_str();
                    ssize_t total = 0,size = 0;
                    while(total < http_request.content_length && \
                            (size = write(downfd[1],query+total,http_request.content_length - total)) > 0){
                        total += size;
                    }
                }

                std::string& body = http_resbonse.resbonse_body;
                char ch = 0;
                while(read(upfd[0],&ch,1) > 0){
                    body.push_back(ch);
                }
                int status = 0;
                int ret = waitpid(pid,&status,0);
                if(ret == pid){
                    if(WIFEXITED(status) && !WEXITSTATUS(status)){
                        return OK;
                    } 
                    else {
                        LOG(ERROR,"----------");
                        return ERROR_SERVER;
                    }
                }
                close(upfd[0]);
                close(downfd[1]);
            }
            else{
                LOG(ERROR,"Create SonProcess Unsucess");
                return 404;
            }
        }

        void Build_HttpResbonseHelper(){
            int code = http_resbonse.status_code;
            auto& status_line = http_resbonse.resbonse_line;
            status_line = VERSION; 
            status_line += " ";
            status_line += std::to_string(code);
            status_line += " ";
            status_line += StatusCodeDesc(code);
            status_line += LINE_END;

            if(code == 200){
                BuildOKResbonse();
            }
            else {
                BuildErrorResbonse(code);
            }
        }
        void BuildOKResbonse(){
            std::string line = "Content-Length: ";
            if(http_request.cgi){
                line += std::to_string(http_resbonse.resbonse_body.size());
            }
            else {
                line += std::to_string(http_request.size);
            }
            line+=LINE_END;
            http_resbonse.resbonse_header.push_back(line);

            line = "Content-Type: ";
            line += SuffixDesc(http_request.suffix);
            line += LINE_END;
            http_resbonse.resbonse_header.push_back(line);
        }
        void BuildErrorResbonse(int code){
            http_request.cgi = false;
            std::string page = GetErrorFile(code); 
            std::cout<<"page"<<page<<std::endl;
            http_resbonse.fd = open(page.c_str(),O_RDONLY);
            if(http_resbonse.fd > 0){
                struct stat buf;
                stat(page.c_str(),&buf);
                http_request.size = buf.st_size;

                std::string line = "Content-type: text/html";
                line += LINE_END;
                http_resbonse.resbonse_header.push_back(line);

                line = "Content-Length: ";
                line += std::to_string(buf.st_size);
                line += LINE_END;
                http_resbonse.resbonse_header.push_back(line);
            }
            
        }

        void Send_HttpResbonse(){
            auto& line = http_resbonse.resbonse_line;

            send(sock,line.c_str(),line.size(),0);
            for(auto& item : http_resbonse.resbonse_header){
                send(sock,item.c_str(),item.size(),0);
            }
            send(sock,http_resbonse.blank.c_str(),http_resbonse.blank.size(),0);
            if(http_request.cgi){
                send(sock,http_resbonse.resbonse_body.c_str(),http_resbonse.resbonse_body.size(),0);
            }
            else {
                sendfile(sock,http_resbonse.fd,nullptr,http_request.size);
                close(http_resbonse.fd);
            }
        }

        ~EndPoint(){
            close(sock);
        }
};



class CALLBACK{
    public:
        CALLBACK(){}

        void operator()(int sock){
            LOG(INFO,"Begin process request");
            HandlerRequest(sock);
        }

        void HandlerRequest(int sock){
            EndPoint ep(sock);
            ep.RecvHttpRequest();
            if(!ep.IsStop()){
                LOG(INFO,"RECV SUCCESS,BEGIN BUILD AND SEND");
                ep.Build_HttpResbonse();
                ep.Send_HttpResbonse();
                LOG(INFO,"Reuqest Process end");
            }
            else{
                LOG(INFO,"RECV ERROR,STOP BUILD AND SEND");
            }
        }


        ~CALLBACK(){}
};
